/**
 * Team members:
 * HengXing Liu 2022229044
 * YiYang Zhang 2022229047
 * ZeJun Liu 2022229092
 */
package project;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.rmi.server.ExportException;
import java.util.*;

public class CommandEvaluator {

    private PrecisionManage pm; //精度管理对象
    private VariableManage vm; //变量管理对象
    private LogManage lm; //日志管理对象

    private FunctionManage fm;//自定义函数管理对象

    private MathematicalEvaluator me;

    private final String identifiers = "setprecision,let,reset,last,save,load,saved,log,end,logged,define";
    private final Set<String> setOfIdt = new HashSet<>(Arrays.asList(identifiers.split(",")));
    // ...

    private Tokenizer tokenizer;

    public CommandEvaluator() {
        pm = new PrecisionManage();
        vm = new VariableManage();
        lm = new LogManage();
        me = new MathematicalEvaluator();
        fm = new FunctionManage();
    }

    public void setTokenizer(Tokenizer tokenizer) {
        this.tokenizer = tokenizer;
    }

    private String printBigDecimal(BigDecimal result) {
        String printResult = "";
        if(new BigDecimal(result.intValue()).compareTo(result) == 0){
            printResult = String.valueOf(result.intValue());
        }else{
            printResult = String.valueOf(result.doubleValue());
        }
        return printResult;
    }

    public void evaluator() throws TokenException, VariableException, LexicalErrorException, IOException, Exception {
        Token token = tokenizer.peekNextToken();
        if(token.isNumber() || (token.isOperator() && ((token.getOperator().equals("(")) || token.getOperator().equals("-U")))){
            me.setTokenizer(tokenizer);
            BigDecimal result = me.Evaluator(vm, pm, fm);

            String printResult = printBigDecimal(result);
            System.out.println(printResult);
            vm.updLast(result.doubleValue());
            if(LogManage.flag){
                LogManage.WriteFile(printResult + "\n");
            }
        }else if(token.isIdentifier()){
            String idt = token.getIdentifier();
            if(idt.equals("last") || !setOfIdt.contains(idt) || idt.equals("sin") || idt.equals("cos") || idt.equals("tan") || idt.equals("abs")){
                me.setTokenizer(tokenizer);
                BigDecimal result = me.Evaluator(vm, pm, fm);

                String printResult = printBigDecimal(result);
                System.out.println(printResult);
                vm.updLast(result.doubleValue());
                if(LogManage.flag){
                    LogManage.WriteFile(printResult + "\n");
                }
            }else{
                if (idt.equals("setprecision")) {
                    precisionManaging();
                }
                else if (idt.equals("let") || idt.equals("reset")) {
                    String command = tokenizer.readNextToken().getIdentifier();
                    variableManaging(command);
                }
                else if (idt.equals("save") || idt.equals("saved") || idt.equals("load")) {
                    savingVariables();
                }
                else if(idt.equals("log")){
                    logManaging();
                }
                else if(idt.equals("logged")){
                    getLogFilename();
                }
                else if(idt.equals("define")){
                    functionManaging();
                }
            }
        }
        else {
            throw new SyntaxErrorException();
        }
    }
    private void functionManaging() throws Exception {
        tokenizer.readNextToken();//搞掉“define”
        if(!tokenizer.hasNextToken()){

            ArrayList<String> allFunction = fm.getAllName();
            if(allFunction.isEmpty()){
                System.out.println("no function defined");
                if(LogManage.flag){
                    LogManage.WriteFile("no function defined" + "\n");
                }
                return;
            }
            Map<String, ArrayList<String>> allVarList = fm.getAllVariable();
            Map<String,String> allExpression = fm.getAllExpression();
            for(int i = 0;i < allFunction.size();i++){
                String funName = allFunction.get(i);
                String output = funName + "(";
                ArrayList<String> nowVariables = allVarList.get(funName);
                output += nowVariables.toString().substring(1,nowVariables.toString().length()-1);
                output += ") = " + allExpression.get(funName);
                System.out.println(output);
                if(LogManage.flag){
                    LogManage.WriteFile(output + "\n");
                }
            }
        }else{
            ArrayList<String> tmpVariable = new ArrayList<>();
            //获取函数名
            String fName = tokenizer.readNextToken().getIdentifier();
            tokenizer.readNextToken();//左括号
            while(tokenizer.hasNextToken()){
                if(tokenizer.peekNextToken().toString().equals(")"))break;
                Token token = tokenizer.readNextToken();
                if(!token.isDelimiter()) {
                    tmpVariable.add(token.getIdentifier());
                }
            }
            tokenizer.readNextToken();//右括号
            tokenizer.readNextToken();//等号
            String res = fm.addOrModify(fName,tmpVariable ,tokenizer);
            System.out.println(res);
            if(LogManage.flag){
                LogManage.WriteFile(res + "\n");
            }
        }
    }
    private void precisionManaging() throws Exception {
        String command = tokenizer.readNextToken().getIdentifier();
        if (tokenizer.hasNextToken()) {
            int precisionNum = (int) tokenizer.readNextToken().getNumber();
            System.out.println("precision set to " + precisionNum);
            if(LogManage.flag){
                LogManage.WriteFile("precision set to " + precisionNum + "\n");
            }
            this.pm.setCurrentPrecision(precisionNum);
        } else {
            System.out.println("current precision is " + PrecisionManage.getCurrentPrecision());
            if(LogManage.flag){
                LogManage.WriteFile("current precision is " + PrecisionManage.getCurrentPrecision() + "\n");
            }
        }

    }
    private void variableManaging(String command) throws Exception {
        if (command.equals("let")) {
            if (tokenizer.hasNextToken()) {//"let" have parameters
                String variableName = tokenizer.readNextToken().getIdentifier();
                tokenizer.readNextToken();
                MathematicalEvaluator mathematicalEvaluator = new MathematicalEvaluator();
                mathematicalEvaluator.setTokenizer(tokenizer);
                BigDecimal res;
                res = mathematicalEvaluator.Evaluator(vm, pm, fm);
                vm.addOrModify(variableName, res.doubleValue());

                String printResult = printBigDecimal(res);

                System.out.println(printResult);
                if(LogManage.flag){
                    LogManage.WriteFile(printResult + "\n");
                }
            }
            else {//"let" does not contain parameters
                Map<String, Double> variables = vm.getAllValue();
                if(variables.size() == 1){
                    System.out.println("no variable defined");
                    if(LogManage.flag){
                        LogManage.WriteFile("no variable defined\n" );
                    }
                    return ;
                }
                for (Map.Entry<String, Double> entry : variables.entrySet()) {
                    if(entry.getKey().equals("last")) continue;
                    System.out.println(entry.getKey() + " = " + entry.getValue());
                    if(LogManage.flag){
                        LogManage.WriteFile(entry.getKey() + " = " + entry.getValue() + "\n");
                    }
                }
            }
        }
        else if (command.equals("reset")) {//reset variable or function
            List<String> variableNames = new ArrayList<>();
            List<String> functionNames = new ArrayList<>();
            boolean flgV = false, flgF = false;
            if(!tokenizer.hasNextToken()){//"reset" does not contain parameters
                flgV = flgF =true;
            }
            while(tokenizer.hasNextToken()){//"reset" have parameters
                String variable = tokenizer.readNextToken().getIdentifier();
                if(fm.isContainFName(variable)){
                    flgF = true;
                    functionNames.add(variable);
                }else{
                    flgV = true;
                    variableNames.add(variable);
                }
            }
            vm.remove(variableNames,flgV);
            fm.remove(functionNames,flgF);
        }
    }

    private void savingVariables() throws Exception {
        Token token = tokenizer.readNextToken();
        if(token.getIdentifier().equals("save")){
            Token fileNameToken = tokenizer.readNextToken();
            String fileName = fileNameToken.getString().replace("\"","");
            List<String> savedVariables = new ArrayList<>();
            while(tokenizer.hasNextToken()){
                Token singleVariableName = tokenizer.readNextToken();
                savedVariables.add(singleVariableName.getIdentifier());
            }
            vm.save(fileName, savedVariables, fm);
        }else if(token.getIdentifier().equals("saved")){
            vm.listSavedDir();
        }else{//load
            Token fileNameToken = tokenizer.readNextToken();
            String fileName = fileNameToken.getString().replace("\"","");
            vm.load(fileName,fm);
        }
    }

    public void logManaging() throws Exception{
        tokenizer.readNextToken();
        if(tokenizer.hasNextToken()) {
            if (tokenizer.peekNextToken().isIdentifier()) {
                if (tokenizer.peekNextToken().getIdentifier().equals("end")) {
                    lm.EndRecord();
                } else {
                    throw new Exception("error identifier");
                }
            } else {
                String filename = tokenizer.readNextToken().getString().replace("\"", "");
                File [] files = lm.getLogFileName();
                int i;
                for( i = 0; i < files.length; i++){
                    if(files[i].getName().equals(filename)){
                        break;
                    }
                }
                if(i<files.length){
                    throw new Exception("this file name already exists");
                }
                lm.SetFilename(filename);
                lm.BeaginRecord();
            }
        } else{
            if(!LogManage.flag){
                System.out.println("No current session!");
            }else{
                System.out.println(lm.filename);
                if(LogManage.flag){
                    LogManage.WriteFile(lm.filename+ "\n");
                }
            }

        }
    }
    public void getLogFilename() throws Exception {
        File [] files = lm.getLogFileName();
        for(File file: files){
            System.out.print(file.getName() + "    ");
            if(LogManage.flag){
                LogManage.WriteFile(file.getName() + "   ");
            }
        }
        System.out.println();
        if(LogManage.flag){
            LogManage.WriteFile("\n");
        }
    }

}
